home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Aminet 52
/
Aminet 52 (2002)(GTI - Schatztruhe)[!][Dec 2002].iso
/
Aminet
/
comm
/
www
/
surfboard-mos.lha
/
surfboard
/
surfboard.c
< prev
next >
Wrap
C/C++ Source or Header
|
2002-10-23
|
22KB
|
807 lines
#include <errno.h> /* obligatory includes */
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <netdb.h>
#include <fcntl.h>
#include <time.h>
#include <sys/stat.h>
#include <unistd.h>
/* Log level constants */
#define LOG_DEBUG 0
#define LOG_MSG 1
#define LOG_REQ 2
#define LOG_ERR 3
#define VERSION "Meredydd Luff's Surfboard/1.1.6 (UNIX/beta)"
#define CGI_ENV_LEN 64
/* ^^^^^^^^^^^ = max. number of environment variables for CGI script*/
char hostname[1024]="";
/* This can be overridden from the command line */
char conffile[1024] = "/etc/surfboard/surfboard.conf";
/* All of these can be overridden from the config file */
char docroot[1024] = "/pub";
char logfile[1024] = "/var/log/surfboard/httpd.log";
char mimefile[1024] = "/etc/surfboard/mime.conf";
char mime_default[512] = "text/plain";
char dirindex[1024] = "index.html";
int myport=80;
int uid=99; /* "nobody" on my machine */
int gid=99; /* "nogroup" for me */
int log_threshold=LOG_MSG; /* By default, log everything bar debugs */
int header_max=1024;
void process(int, char *, int);
void readconf(void);
void log_msg(int level, char * msg);
char * getextension(char *);
void cgi_setenv(char **, char *, char *);
void getmime(char *, char *, char *);
void dumperror(int, int, char *);
void mk_index(int, char *, char *, int);
void fillout_header(char *);
void add2header(char*, char *);
char readstr(int, char *, int);
void writestr(int, char *);
void chomp(char *);
int stripqmark(char *);
int setupsock(void);
void fireman(void);
main(int argc, char * argv[])
{
int s, a;
if(argc>1) { strcpy(conffile, argv[1]); }
readconf();
log_msg(LOG_MSG, "Surfboard started");
if((s=setupsock())<0)
{
log_msg(LOG_ERR, "Could not open socket");
perror("Could not open socket");
return 1;
}
signal(SIGCHLD, fireman);
if(fork()) { exit(0); }
while(1)
{
int ns;
while((ns=accept(s, NULL, NULL))<0);
if(!fork()) {
close(ns);
} else {
if(uid) { setuid(uid); }
if(gid) { setgid(gid); }
process(ns, docroot, s);
exit(0);
}
}
}
/************************************************************************/
void process(int s, char * docroot, int orgs)
{
char * http_header;
char http_version[10]="NONE";
int do_header=0;
char rawreq[1024];
char content_type[512];
char handler[1024]="dump";
char req[2048];
char method[10];
char buf[2048];
int methodok=0;
struct stat file_inf;
log_msg(LOG_DEBUG, "Request received");
http_header=(char *)malloc(header_max * sizeof(char));
http_header[0]='\0';
readstr(s, method, 10);
if(!strcmp(method, "GET"))
{
methodok=1;
}
if(!methodok)
{
sprintf(buf, "Unknown method \"%s\", bailing out with a 400", method);
log_msg(LOG_ERR, buf);
add2header(http_header, "HTTP/1.1 400 Bad Request\r\n");
fillout_header(http_header);
add2header(http_header, "Content-type: text/html\r\n\r\n");
if(do_header) { writestr(s, http_header); }
dumperror(s, 400, "");
exit(0);
}
if(readstr(s, rawreq, 1024)==' ')
{
char c=' ', oldc='\n';
readstr(s, http_version, 10);
do_header=1;
/* Flush the browser options - perhaps I'll take some notice of them
sometime ;-)
*/
while(1)
{
while(read(s, &c, 1)<1);
if(c=='\r') { continue; }
if(c=='\n' && oldc=='\n') { break; }
oldc=c;
}
} else {
do_header=0;
}
sprintf(buf, "Asked for %s", rawreq);
log_msg(LOG_DEBUG, buf);
if(strstr(rawreq, "..") || strstr(rawreq, "`"))
{
log_msg(LOG_ERR, "Relative path and/or shell escape - ATTACK ATTEMPT");
add2header(http_header, "HTTP/1.1 400 Bad Request\r\n");
fillout_header(http_header);
add2header(http_header, "Content-type: text/html\r\n\r\n");
if(do_header) { writestr(s, http_header); }
dumperror(s, 400, "");
exit(0);
}
strcpy(req, docroot);
strcat(req, rawreq);
if(strstr(req, "?"))
{
char * p;
p=(char *)strstr(req, "?");
*p='\0';
}
errno=0;
stat(req, &file_inf);
if(errno==ENOENT)
{
add2header(http_header, "HTTP/1.1 404 Not Found\r\n");
fillout_header(http_header);
add2header(http_header, "Content-type: text/html\r\n\r\n");
if(do_header) { writestr(s, http_header); }
sprintf(buf, "%s %s %s 404", method, rawreq, http_version);
log_msg(LOG_REQ, buf);
dumperror(s, 404, rawreq);
exit(0);
}
if(S_ISDIR(file_inf.st_mode))
{
log_msg(LOG_DEBUG, "Indexing directory");
mk_index(s, req, rawreq, do_header);
/* also handles redirect to put slashes on dir names*/
sprintf(buf, "Finished indexing, now requesting %s", req);
log_msg(LOG_DEBUG, buf);
}
log_msg(LOG_DEBUG, "Determining MIME type...");
getmime(req, content_type, handler);
sprintf(buf, "Determined MIME type: %s (action:%s)", content_type, handler);
log_msg(LOG_DEBUG, buf);
if(!strcmp(handler, "dump"))
{
int rdhandle;
log_msg(LOG_DEBUG, "Dumping file");
errno=0;
rdhandle=open(req, O_RDONLY);
switch(errno)
{
case(0) : { break; }
default : {
add2header(http_header, "HTTP/1.1 499 Unknown Error\r\n");
fillout_header(http_header);
add2header(http_header, "Content-type: text/html\r\n\r\n");
if(do_header) { writestr(s, http_header); }
sprintf(buf, "%s %s %s 499", method, rawreq, http_version);
log_msg(LOG_REQ, buf);
dumperror(s, 499, rawreq);
exit(0);
}
}
http_header[0]='\0';
add2header(http_header, "HTTP/1.1 200 OK\r\n");
fillout_header(http_header);
add2header(http_header, "Content-type: ");
add2header(http_header, content_type);
add2header(http_header, "\r\n\r\n");
if(do_header) { writestr(s, http_header); }
sprintf(buf, "%s %s %s 200", method, rawreq, http_version);
log_msg(LOG_REQ, buf);
stat(req, &file_inf);
while(file_inf.st_size>0)
{
int dlen;
dlen=read(rdhandle, buf, 1024);
file_inf.st_size-=dlen;
write(s, buf, dlen);
}
close(rdhandle);
log_msg(LOG_DEBUG, "File closed, going to the big exit statement in the sky...");
}
if(handler[0]=='/' || !strcmp(handler, "cgi") || !strcmp(handler, "cgi-nph"))
{
int qpos=0;
char * cgienv[CGI_ENV_LEN];
char path[1024];
strcpy(path, req);
for(qpos=strlen(path)-1; qpos>0; qpos--)
{
if(path[qpos]=='/') { break; }
path[qpos]='\0';
}
chdir(path);
log_msg(LOG_DEBUG, "Identified as CGI script/handler");
if(handler[0]=='/') { stat(handler, &file_inf); }
if(!(file_inf.st_mode&S_IXOTH))
{
http_header[0]='\0';
if(do_header)
{
add2header(http_header, "HTTP/1.1 403 Forbidden\r\n");
fillout_header(http_header);
add2header(http_header, "Content-type: text/html\r\n\r\n");
writestr(s, http_header);
}
sprintf(buf, "%s %s %s 403", method, rawreq, http_version);
log_msg(LOG_REQ, buf);
dumperror(s, 403, rawreq);
exit(0);
}
if(!strcmp(handler, "cgi"))
{
log_msg(LOG_DEBUG, "Not NPH, building header");
http_header[0]='\0';
add2header(http_header, "HTTP/1.1 200 OK\r\n");
fillout_header(http_header);
writestr(s, http_header); /* Broken if HTTP/x.x not specified! */
}
close(1); /* stdout */
log_msg(LOG_DEBUG, "stdout closed");
if(dup(s)!=1)
{
log_msg(LOG_ERR, "CGI script stdout redirection failed");
http_header[0]='\0';
add2header(http_header, "HTTP/1.1 500 Internal Server Error\r\n");
fillout_header(http_header);
if(do_header) { writestr(s, http_header); }
dumperror(s, 500, "");
}
log_msg(LOG_DEBUG, "Setting up environment variables");
cgienv[0]=NULL;
/* Got this list from the CGI 1.1 specification
(http://hoohoo.ncsa.uiuc.edu/cgi/)
*/
cgi_setenv(cgienv, "SERVER_SOFTWARE", VERSION);
cgi_setenv(cgienv, "SERVER_NAME", hostname);
cgi_setenv(cgienv, "GATEWAY_INTERFACE", "CGI/1.1");
cgi_setenv(cgienv, "SERVER_PROTOCOL", http_version);
sprintf(buf, "%d", myport);
cgi_setenv(cgienv, "SERVER_PORT", buf);
cgi_setenv(cgienv, "REQUEST_METHOD", method);
/*
Neither PATH_INFO nor PATH_TRANSLATED are set, as I haven't a clue
what they are! Any help to m_luff@wincoll.ac.uk
*/
log_msg(LOG_DEBUG, "Stripping arguments...");
qpos=stripqmark(rawreq);
cgi_setenv(cgienv, "SCRIPT_NAME", rawreq);
log_msg(LOG_DEBUG, "Stripped arguments OK");
/* cgi_setenv(cgienv, "REMOTE_ADDR", ); */ /* STORE IT */
/* cgi_setenv(cgienv, "REMOTE_HOST", ); - Leaving unset */
/* AUTH_TYPE is not set */
/* REMOTE_USER is not set */
/* REMOTE_IDENT is not set */
/* CONTENT_TYPE is not set */
/* CONTENT_LENGTH is not set */
/* One day I'll put in HTTP_xxx from the client */
if(handler[0]=='/')
{
log_msg(LOG_DEBUG, "Launching CGI handler");
cgi_setenv(cgienv, "QUERY_STRING", rawreq+qpos);
execle(handler, handler, req, NULL, cgienv);
} else if(strstr(rawreq+qpos, "=") || *(rawreq+qpos)=='\0') {
log_msg(LOG_DEBUG, "Query string");
cgi_setenv(cgienv, "QUERY_STRING", rawreq+qpos);
execle(req, req, NULL, cgienv);
} else {
log_msg(LOG_DEBUG, "Command-line option");
execle(req, req, rawreq+qpos, NULL, cgienv);
}
log_msg(LOG_ERR, "execle() call returned!");
perror("execle()");
dup(2); /* stderr is copied onto the first free descriptor - stdout,
in case something went wrong */
}
free(http_header);
close(s);
}
/************************************************************************/
void dumperror(int s, int num, char * req)
{
char buf[1024];
switch(num)
{
case(400) : {
writestr(s, "<HTML>\n<HEAD><TITLE>400 Bad Request</TITLE></HEAD>\n");
writestr(s, "<BODY>\n<H1 ALIGN=CENTER>400 Bad Request</H1>\n");
writestr(s, "<P ALIGN=CENTER><I>Your client sent an invalid request, ");
writestr(s, "or one which the server cannot deal with.</I></P>\n");
writestr(s, "<P ALIGN=RIGHT>" VERSION "</P>\n</BODY>\n</HTML>\n");
close(s);
break;
}
case(403) : {
writestr(s, "<HTML>\n<HEAD><TITLE>403 Permission Denied</TITLE></HEAD>\n");
writestr(s, "<BODY>\n<H1 ALIGN=CENTER>403 Permission Denied</H1>\n");
writestr(s, "<P ALIGN=CENTER><I>You do not have permission to ");
sprintf(buf, "access %s on this server</I></P>\n<P ALIGN=RIGHT>" VERSION
"</P>\n</BODY>\n</HTML>\n", req);
writestr(s, buf);
close(s);
break;
}
case(404) : {
writestr(s, "<HTML>\n<HEAD><TITLE>404 Not Found</TITLE></HEAD>\n");
writestr(s, "<BODY>\n<H1 ALIGN=CENTER>404 Not Found</H1>\n");
writestr(s, "<P ALIGN=CENTER><I>The specified file was not ");
writestr(s, "found</I></P>\n<P ALIGN=RIGHT>" VERSION
"</P>\n</BODY>\n</HTML>\n");
close(s);
break;
}
case(499) : {
writestr(s, "<HTML>\n<HEAD><TITLE>499 Unknown Error</TITLE></HEAD>\n");
writestr(s, "<BODY>\n<H1 ALIGN=CENTER>499 Unknown Error</H1>\n");
writestr(s, "<P ALIGN=CENTER><I>An error occurred while opening ");
writestr(s, "this file. What it is, I do not know! Contact ");
writestr(s, "the webmaster.</I></P>\n<P ALIGN=RIGHT>" VERSION
"</P>\n</BODY>\n</HTML>\n");
close(s);
break;
}
case(500) : {
writestr(s, "<HTML>\n<HEAD><TITLE>500 Internal Server Error</TITLE></HEAD>\n");
writestr(s, "<BODY>\n<H1 ALIGN=CENTER>500 Internal Server Error</H1>\n");
writestr(s, "<P ALIGN=CENTER><I>Something went wrong inside"
" the server. Perhaps a CGI script went awry, or something "
"in the bowels of the httpd is broken. Get your admin to check the "
"logfile for error messages.</I></P>\n<P ALIGN=RIGHT>" VERSION
"</P>\n</BODY>\n</HTML>\n");
close(s);
break;
}
default : {
sprintf(buf, "<HTML>\n<HEAD><TITLE>%d - Unhandled HTTP Error"
"</TITLE></HEAD>\n<BODY><H1 ALIGN=CENTER>%d - Unhandled HTTP "
"Error</H1>\n<P ALIGN=CENTER><I>This is a bug in the web server. "
"The server has trapped an error and knows what went wrong, but "
"the author has forgotten to write an explanation of this "
"error code. Mail Meredydd Luff (at <A "
"HREF=mailto:m_luff@wincoll.ac.uk>m_luff@wincoll.ac.uk</A>) "
"and tell him what happened.</I></P>\n<P ALIGN=RIGHT>" VERSION
"</P>\n</BODY>\n</HTML>\n", num, num);
writestr(s, buf);
close(s);
break;
}
}
}
/************************************************************************/
void cgi_setenv(char ** env, char * name, char * value)
{
int a=0;
while(env[a]!=NULL) { a++; }
if(a==CGI_ENV_LEN-1)
{
char buf[1024];
sprintf(buf, "CGI environment full - cannot add %s=%s", name, value);
log_msg(LOG_ERR, buf);
return;
}
env[a+1]=NULL;
env[a]=(char *)malloc((strlen(name)+strlen(value)+64)*sizeof(char));
strcpy(env[a], name);
strcat(env[a], "=");
strcat(env[a], value);
}
/************************************************************************/
void redirect(int s, char * from, char * to, int do_header)
{
char buf[4096];
char * http_header;
http_header=(char *)malloc(header_max*sizeof(char));
http_header[0]='\0';
add2header(http_header, "HTTP/1.1 301 Moved Permanently\r\n");
fillout_header(http_header);
add2header(http_header, "Location: ");
add2header(http_header, to);
add2header(http_header, "\r\n\r\n");
if(do_header) { writestr(s, http_header); }
sprintf(buf, "<HTML>\n<HEAD><TITLE>301 Moved Permanently</TITLE>"
"</HEAD>\n<BODY><H1 ALIGN=CENTER>301 Moved Permanently</H1>\n"
"<P ALIGN=CENTER><I>The URL %s has moved to <A HREF=\"%s\">%s</A>"
"</I></P>\n<P ALIGN=RIGHT>" VERSION "</P>\n</BODY>\n</HTML>\n",
from, to, to);
writestr(s, buf);
free(http_header);
}
/************************************************************************/
void mk_index(int s, char * req, char * rawreq, int do_header)
{
char buf[4096];
int pos=0;
int p=0, do404=0;
struct stat file_inf;
if(req[strlen(req)-1]!='/')
{
char http_header[1024]="";
char corrected[1024];
strcpy(corrected, rawreq);
strcat(corrected, "/");
redirect(s, rawreq, corrected, do_header);
exit(0);
}
errno=0;
log_msg(LOG_DEBUG, "Trying:");
do
{
strcpy(buf, req);
p=pos;
while(!(dirindex[p]=='\0' || isspace(dirindex[p]))) { p++; }
if(dirindex[p]=='\0') { do404=1; }
dirindex[p]='\0';
if(dirindex[pos]=='/')
{ strcpy(buf, dirindex+pos); } else { strcat(buf, dirindex+pos); }
log_msg(LOG_DEBUG, buf);
errno=0;
stat(buf, &file_inf);
if(S_ISDIR(file_inf.st_mode)) { errno=ENOENT; }
pos=p+1;
} while(errno==ENOENT && !do404);
if(do404 && errno==ENOENT)
{
char * http_header;
http_header=(char *)malloc(header_max*sizeof(char));
http_header[0]='\0';
add2header(http_header, "HTTP/1.1 404 Not Found\r\n");
fillout_header(http_header);
add2header(http_header, "Content-type: text/html\r\n\r\n");
if(do_header) { writestr(s, http_header); }
dumperror(s, 404, rawreq);
free(http_header);
exit(0);
} else {
strcpy(req, buf);
}
}
/************************************************************************/
void fillout_header(char * header)
{
add2header(header, "Server: " VERSION "\r\n");
add2header(header, "Connection: close\r\n");
}
/************************************************************************/
void getmime(char * req, char * content_type, char * handler)
{
FILE * mime;
char buf[4096];
strcpy(content_type, mime_default);
strcpy(handler, "dump");
if((mime=fopen(mimefile, "r"))==NULL)
{
sprintf(buf, "Could not open MIME config file \"%s\", using default "
"content type %s\n", mimefile, mime_default);
log_msg(LOG_ERR, buf);
strcpy(content_type, mime_default);
} else {
char thisext[512];
char thistype[512];
char thishandler[1024];
char myext[512];
strcpy(myext, getextension(req));
while(!feof(mime))
{
fscanf(mime, " %s", thisext);
if(thisext[0]=='#') { while(getc(mime)!='\n'); continue; }
fscanf(mime, " %s ", thistype);
fgets(thishandler, 1024, mime);
chomp(thishandler);
if(!strcmp(myext, thisext))
{ strcpy(content_type, thistype); strcpy(handler, thishandler); break; }
}
fclose(mime);
}
}
/************************************************************************/
void add2header(char * header, char * line)
{
if(strlen(header)+strlen(line)+1>header_max)
{
log_msg(LOG_ERR, "Header exceeded max length, line ignored");
} else {
strcat(header, line);
}
}
/************************************************************************/
void readconf(void)
{
FILE * conf;
char cmd[512];
char buf[1600];
sprintf(buf, "Reading config from file \"%s\"", conffile);
log_msg(LOG_DEBUG, buf);
if((conf=fopen(conffile, "r"))==NULL)
{
sprintf(buf, "Could not open config file \"%s\"", conffile);
log_msg(LOG_ERR, buf);
fprintf(stderr, "%s\n", buf);
exit(1);
}
while(1)
{
fscanf(conf, " %s ", cmd);
if(feof(conf)) { break; }
if(cmd[0]=='#') { while(getc(conf)!='\n'); continue; }
if(!strcmp(cmd, "Port"))
{
fscanf(conf, " %d", &myport);
continue;
}
if(!strcmp(cmd, "DocRoot"))
{
fscanf(conf, " %s", docroot);
continue;
}
if(!strcmp(cmd, "LogFile"))
{
fscanf(conf, " %s", logfile);
continue;
}
if(!strcmp(cmd, "LogThreshold"))
{
fscanf(conf, " %d", &log_threshold);
continue;
}
if(!strcmp(cmd, "MimeFile"))
{
fscanf(conf, " %s", mimefile);
continue;
}
if(!strcmp(cmd, "DefaultMime"))
{
fscanf(conf, " %s", mime_default);
continue;
}
if(!strcmp(cmd, "SetUID"))
{
fscanf(conf, " %d", &uid);
continue;
}
if(!strcmp(cmd, "SetGID"))
{
fscanf(conf, " %d", &gid);
continue;
}
if(!strcmp(cmd, "DirIndex"))
{
fgets(dirindex, 1024, conf);
chomp(dirindex);
continue;
}
fprintf(stderr, "Surfboard - Invalid config option \"%s\"\n", cmd);
fclose(conf);
exit(1);
}
fclose(conf);
log_msg(LOG_DEBUG, "Finished reading config file");
}
/************************************************************************/
void log_msg(int level, char * str)
{
if(level>=log_threshold)
{
if(!strcmp(logfile, "-"))
{
fprintf(stderr, "Surfboard - Log level %d: %s\n", level, str);
} else {
FILE * logfileptr;
int a;
time_t now;
time(&now);
for(a=0; a<5; a++)
{
logfileptr=fopen(logfile, "a");
if(logfileptr==NULL) { continue; }
fprintf(logfileptr, "%d %s%s\n", level, ctime(&now), str);
fclose(logfileptr);
return;
}
printf("Surfboard - Cannot open logfile!\a\n");
perror("Surfboard - Cannot open logfile!\a");
printf("Surfboard - Log level %d: %s\n", level, str);
}
}
}
/************************************************************************/
char * getextension(char * s)
{
char scopy[1024];
char * retval;
strcpy(scopy, s);
retval=strstr(scopy, "?");
if(retval==NULL)
{ retval=scopy+strlen(s); } else { *retval='\0'; }
while(1)
{
retval--;
if(*retval=='.')
{ log_msg(LOG_DEBUG, retval+1);
return (retval+1); }
if(retval==scopy) { return s; }
}
}
/************************************************************************/
void writestr(int s, char * buf)
{
write(s, buf, strlen(buf));
}
/************************************************************************/
void chomp(char * s)
{
if(s[0]=='\0') { return; }
if(s[strlen(s)-1]=='\n') { s[strlen(s)-1]='\0'; }
}
/************************************************************************/
int stripqmark(char * s)
{
int pos=0;
do
{
if(s[pos]=='?') { s[pos]='\0'; return pos+1; }
pos++;
} while(s[pos]!='\0');
return pos;
}
/************************************************************************/
char readstr(int s, char * buf, int maxlen)
{
char c;
int pos=0;
while(1)
{
while(read(s, &c, 1)<1);
if(c=='\r') { continue; } /* There's a \n coming... */
if(isspace(c) || pos==maxlen-1) { buf[pos]='\0'; return c; }
buf[pos]=c;
pos++;
}
}
/************************************************************************/
int setupsock(void)
{
int s;
struct sockaddr_in myopen;
struct hostent *hp;
memset(&myopen, 0, sizeof(struct sockaddr_in)); /* clear my address */
gethostname(hostname, 1024); /* who are we? (also
store for later) */
hp= gethostbyname(hostname); /* get our address info */
if (hp == NULL) /* we don't exist !? */
return(-1);
myopen.sin_family= hp->h_addrtype; /* this is our host address */
myopen.sin_port= htons(myport); /* this is our port number */
if ((s= socket(AF_INET, SOCK_STREAM, 0)) < 0) /* create socket */
return(-1);
if (bind(s,(struct sockaddr *)&myopen,sizeof(struct sockaddr_in)) < 0) {
close(s);
return(-1); /* bind address to socket */
}
listen(s, 3); /* max # of queued connects */
return(s);
}
/*************************************************************************/
/* Fireman - anti-zombie code. Greek to me, but it works ;-) */
void fireman(void)
{
while (waitpid(-1, NULL, WNOHANG) > 0);
}